﻿using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Windows.Forms;
using BGShell.Components;
using BGShell.PowerShell.Controls;
using BGShell.PowerShell.DataTips;
using BGShell.PowerShell.LanguageService.Threading;
using BGShell.PowerShell.Runspaces;
using BGShell.Interop;
using WindowsFormsAero;

namespace BGShell.PowerShell.LanguageService
{
    internal sealed partial class PSLanguageService : IDisposable
    {
        [Flags, Serializable]
        public enum UpdateFlags
        {
            None = 0,
            TextChanged = 1,
            NoUpdateSelection = 2,
        }

        private const int ParseDelay = 330;
        private const int ScrollDelay = 100;

        private readonly ConsoleControl _control;
        private readonly ConsoleControl.InputReader _reader;

        private ViewportInfo _viewport;

        private BackgroundThread _bgthread;
        private HighlighterJob _lastJob;

        private String _errorMessage;
        private RichTextRange _errorRange;
        private ParseErrorToolTip _errorBalloon;

        private ConsoleControlFormatSettings _formatSettings;
        private List<EditControlDataTip> _tempTips;
        private TabExpansionHandler _tabExpansion;
        
        private DataTipCache _dataTipCache = new DataTipCache();

        public PSLanguageService(ConsoleControl.InputReader reader, ConsoleControl control)
        {
            _control = control;
            _reader = reader;

            _formatSettings = control.FormatSettings;

            _bgthread = new BackgroundThread("PSLanguageService.BackgroundThread");

            _control.TextChanged += OnTextChanged;
            _control.VScroll += OnVScroll;
        }

        public void Dispose()
        {
            _tempTips = null;

            _control.VScroll -= OnVScroll;
            _control.TextChanged -= OnTextChanged;

            if (_bgthread != null)
            {
                _bgthread.Dispose();
            }

            HideParseErrorBalloon();
        }

        public void Update(Int32 delay, Action callback, UpdateFlags flags)
        {
            Action realCallback;

            if ((flags & UpdateFlags.NoUpdateSelection) == 0)
            {
                realCallback = new Action(UpdateSelection);
                realCallback += callback;
            }
            else
            {
                realCallback = callback;
            }

            if ((flags & UpdateFlags.TextChanged) == UpdateFlags.TextChanged)
            {
                if (_lastJob != null)
                {
                    _lastJob.Cancel();
                    _lastJob = null;
                }

                using (_control.AllowProtectedUpdates())
                {
                    _reader.TextRange.Font.Underline = RichTextUnderline.None;
                }

                if (_tempTips != null)
                {
                    _control.RemoveDataTips(_tempTips);
                    _tempTips.Clear();
                }

                _errorMessage = null;
                _errorRange = null;
            }

            if (_lastJob == null)
            {
                _lastJob = new HighlighterJob(this, _reader.Text);
            }

            _viewport = ViewportInfo.Get(_control, _reader);
            _bgthread.Execute(_lastJob, delay, realCallback);
        }

        public ConsoleControl Control
        {
            get { return _control; }
        }

        public ConsoleControl.InputReader Reader
        {
            get { return _reader; }
        }

        public void TabExpansion(bool shift)
        {
            if (_tabExpansion == null)
            {
                _tabExpansion = new TabExpansionHandler(Control.Runspace, this);
            }

            if (shift)
            {
                _tabExpansion.Prev();
            }
            else
            {
                _tabExpansion.Next();
            }
        }

        //
        // Parse Error Balloon
        //

        public void ShowParseErrorBalloon()
        {
            HideParseErrorBalloon();

            if (HasParseErrors)
            {
                _errorBalloon = new ParseErrorToolTip(this, _errorMessage, _errorRange);
            }
        }

        public void HideParseErrorBalloon()
        {
            if (ParseErrorBalloonVisible)
            {
                _errorBalloon.DestroyHandle();
                _errorBalloon = null;
            }
        }

        public Boolean ParseErrorBalloonVisible
        {
            get { return _errorBalloon != null; }
        }

        public Boolean HasParseErrors
        {
            get { return (_errorMessage != null); }
        }

        //
        // Event handlers
        //

        private void OnTextChanged(object sender, EventArgs e)
        {
            if (_tabExpansion != null)
            {
                _tabExpansion.CancelAsyncRequests();
            }

            HideParseErrorBalloon();
            Update(ParseDelay, null, UpdateFlags.TextChanged);
        }

        private void OnVScroll(object sender, EventArgs e)
        {
            if (!_control.InUnprotectedScope && (_lastJob != null) && (_lastJob.HasTokensRemaining))
            {
                Update(ScrollDelay, null, UpdateFlags.NoUpdateSelection);
            }
        }

        //
        // Token Parsing
        // 

        private void BeginUpdate()
        {
            _control.InvokeIfRequired(_control.BeginUpdate);
        }

        private void EndUpdate()
        {
            _control.InvokeIfRequired(_control.EndUpdate);
        }

        private void UpdateSelection()
        {
            _control.BeginInvokeIfRequired(() => _control.Select(_control.SelectionStart, _control.SelectionLength));
        }

        //
        // Data Tips
        //

        private DataTipItem RequestTip(DataTipInfo request)
        {
            var result = _dataTipCache[request];

            if (result == null)
            {
                _control.Runspace.BeginGetDataTip(AddDataTip, request);
            }

            return result;
        }


        private void AddDataTip(DataTipInfo dataTip)
        {
            _control.BeginInvokeIfRequired(AddDataTipUnsafe, dataTip);
        }

        private void AddDataTipUnsafe(DataTipInfo dataTip)
        {
            var tomRange = GetTomRange(dataTip.Range);

            using (_control.AllowProtectedUpdates())
            {
                AddDataTipUnsafe(tomRange, tomRange.Font, dataTip);
            }
        }

        private void AddDataTipUnsafe(RichTextRange range, RichTextFont font, DataTipInfo info)
        {
            var item = info.Item;

            if (item != null)
            {
                _dataTipCache.Add(info);

                if (item is ErrorDataTip)
                {
                    font.Underline = RichTextUnderline.Wave;
                    font.UnderlineColor = RichTextUnderlineColor.Blue;
                }

                if (_tempTips == null)
                {
                    _tempTips = new List<EditControlDataTip>();
                }

                _tempTips.Add(_control.AddDataTip(range, item));
            }
        }

        private void HighlightRange(DataTipInfo info, PSTokenType tokenType)
        {
            _control.BeginInvokeIfRequired(HighlightRangeUnsafe, info, tokenType);
        }

        private void HighlightRangeUnsafe(DataTipInfo info, PSTokenType tokenType)
        {
            var format = _formatSettings[tokenType];

            var tomRange = GetTomRange(info.Range);
            var tomFont = tomRange.Font;

            using (_control.AllowProtectedUpdates())
            {
                if (format != null)
                {
                    format.Apply(tomFont);
                }

                AddDataTipUnsafe(tomRange, tomFont, info);
            }
        }

        private void HighlightError(TextRange range, String message)
        {
            _control.BeginInvokeIfRequired(HighlightErrorUnsafe, range, message);
        }

        private void HighlightErrorUnsafe(TextRange range, String message)
        {
            using (_control.AllowProtectedUpdates())
            {
                RichTextRange tomRange;

                if (range.Start == range.End)
                {
                    tomRange = _reader.GetLineRange(range.Start);

                    if (tomRange == null)
                    {
                        tomRange = _reader.TextRange;
                    }
                }
                else
                {
                    tomRange = GetTomRange(range);
                }

                _errorRange = tomRange;
                _errorMessage = message;

                tomRange.Font.Underline = RichTextUnderline.Wave;
                tomRange.Font.UnderlineColor = RichTextUnderlineColor.Red;
            }
        }

        //
        // Token Formatting
        //

        private RichTextRange GetTomRange(TextRange range)
        {
            return _control.TextDocument.GetRange(range.Start, range.End);
        }
    }
}
